/*
* ============================================================================
*  Name        : sea.cpp
*  Part of     : Finite state machine framework 
*  Description : 
*  Version     : %version: %
*  
*
* ============================================================================
*/
#include "sea.h"
#include "sea_engine.h"
#include "sea_manager.h"
#include "sea_parser.h"

/**
 * @file sea.cpp
 *
 * @ingroup StateMachineFramework
 */

using namespace sea;

/**
 * Meta system registry global pointer
 */
GlobalMetaSystemRegistry::MetaRegistryEntryType* GlobalMetaSystemRegistry::m_meta_system_registry = NULL;

/**
 * Global registry lock
 */
OsMutex GlobalMetaSystemRegistry::m_lock;

/**
 * System state machine 
 */
SystemStateMachine* StateMachine::m_system = NULL;


/**
 * System state machine static meta events 
 */
#if defined( STATIC_META_DATA_ENABLED )

EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_Create );              ///< start/restart state machine
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_Destroy );             ///< stop state machine
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_OnEntry );             ///< on state entry event
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_Heartbeat );           ///< heart beat timeout elapsed
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_LastHeartbeat );       ///< last heart beat timeout elapsed
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_Terminate );           ///< command/event to terminate state machine
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_ChildTerminated );     ///< child state machine has been stopped
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_ParentTerminated );    ///< parent state machine has been stopped
EVENT_SYS_IMPL( SystemStateMachine, Ev_Sys_NoEvent );             ///< dummy event

#endif
// -------------------------------------------------------------------------------------------------
// Ctor.
// -------------------------------------------------------------------------------------------------
StateMachine::StateMachine( String name ):
    m_meta( name ),
    m_stateStamp( 0 ),
    m_alive( false ),
    m_current_state( 0 ),
    m_manager( NULL ),
    m_parent( NULL ),
    m_parser( NULL ),
    m_variant( 0 ),
    m_exit_notifier( NULL ),
    m_exec_mode( RunDefault ),
    m_in_dispatcher( false )
#if defined( SEA_LOG )
    ,
    m_logging( false )
#endif
{
}

// -------------------------------------------------------------------------------------------------
// Ctor. 
// -------------------------------------------------------------------------------------------------
StateMachine::~StateMachine( )
{
    if ( m_parser && m_parser != this )
    {
        delete m_parser;
    }
}

// -------------------------------------------------------------------------------------------------
// Create state machine compiling the source code
// -------------------------------------------------------------------------------------------------
Error StateMachine::create( i::Index variant, const char* str, Type type /* eString */ )
{
    if ( m_variant == variant && m_body != NULL )
    {
        return eOk;
    }

    MetaSystemRegistryEntry* the_registry = registry( );
    sea::aref compiledStateMachine;

    compiledStateMachine = the_registry->m_variant_map[ variant ];
    if ( compiledStateMachine == NULL )
    {
        if ( m_parser == NULL )
        {
            m_parser = NEW Parser;
            if ( m_parser == NULL )
            {
                ASSERT_DEBUG( false );
                return eOutOfMemory;
            }
        }

        // Compile state machine
        sea::aref local( m_parser->exec( this, str, static_cast< Parser::Type >( type ) ), false );
        compiledStateMachine = local;

        if ( compiledStateMachine == NULL )
        {
            ASSERT_DEBUG( false );
            return eCompileError;
        }

        // Keep reference for the registry
        the_registry->m_variant_map[ variant ] = compiledStateMachine;
    }

    // Store the state machine variant id
    m_variant = variant;

    if ( m_manager != NULL )
    {
        OsMutex::Scoped scoped( m_manager->lock( ) );
        m_body = compiledStateMachine;
    }
    else
    {
        m_body = compiledStateMachine;
    }

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Create state machine with precompiled format
// -------------------------------------------------------------------------------------------------
Error StateMachine::create( i::Index variant, BinCode raw_array )
{
    if ( m_variant == variant && m_body != NULL )
    {
        return eOk;
    }

    MetaSystemRegistryEntry* the_registry = registry( );
    sea::aref compiledStateMachine;

    compiledStateMachine = the_registry->m_variant_map[ variant ];
    if ( compiledStateMachine == NULL )
    {
        Parser* parser = dynamic_cast< Parser* >( this );
        if ( parser == NULL )
        {
            if ( m_parser == NULL )
            {
                m_parser = NEW Parser;
                if ( m_parser == NULL )
                {
                    return eOutOfMemory;
                }
            }
        }
        else
        {
            m_parser = parser;
        }

        // Compile state machine
        sea::aref local( m_parser->exec( this, raw_array ), false );
        compiledStateMachine = local;

        if ( compiledStateMachine == NULL )
        {
            return eCompileError;
        }

        // Keep reference for the registry
        the_registry->m_variant_map[ variant ] = compiledStateMachine;
    }

    // Store the state machine variant id
    m_variant = variant;

    if ( m_manager != NULL )
    {
        OsMutex::Scoped scoped( m_manager->lock( ) );
        m_body = compiledStateMachine;
    }
    else
    {
        m_body = compiledStateMachine;
    }

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Launch state machine instances
// -------------------------------------------------------------------------------------------------
Error StateMachine::start( vector< StateMachine* > state_machines, StateMachine* parent, ExecutionMode mode /* = RunDefault */ )
{
    ExecutionMode exec_mode = mode;
    sea::ref< Manager > manager;

    Engine* the_engine = Engine::engine( ).get( );

    int count = state_machines.size( );
    int already_launched = 0;

    // Create all of them
    for( int i = 0; i < count; ++i )
    {
        StateMachine* the_sm = state_machines[ i ];

        // Check if it was compiled
        if ( the_sm->m_body == NULL )
        {
            return eCreateStateMachineFirst;
        }

        // Skip launched state machines
        if ( the_sm->is_alive( ) )
        {
            already_launched++;
            continue;
        }

        // RunDefault instructs to use the mode in the compiled body
        if ( exec_mode == RunDefault )
        {
            exec_mode = static_cast< ExecutionMode >( the_sm->get_body_field( Parser::eExecMode ) );
        }
        the_sm->set_exec_mode( exec_mode );

        switch ( exec_mode )
        {
            case RunSync:
            {
                // If parent is present we cannot use its manager, b/c it is a nested call..kind of
                if( manager == NULL )
                {
                    manager = the_engine->create_manager( true );
                }

                // If more than one blocking state machine are to be launched, they are blocking
                // only for parent and ASYNC in one thread among children
                break;
            }
            case RunParser:
            {
                if( manager == NULL )
                {
                    manager = the_engine->create_manager( true, true );
                }

                break;
            }
            case RunWithParent:
            {
                if( !parent )
                {
                    return eParentNotPresent;
                }

                if( manager == NULL )
                {
                    manager = parent->manager( );
                }
                break;
            }
            case RunAsyncBatch:
            {
                if( manager == NULL )
                {
                    manager = the_engine->create_manager( false );
                }
                break;
            }
            case RunDispatched:
                if( manager == NULL )
                {
                    // For self dispatched state machines tell engine not to start thread
                    // by saying this is sync state machine... may be it is...
                    manager = the_engine->create_manager( true );
                }
                break;
            
            case RunAsync:
            {
                manager = the_engine->create_manager( false );
                break;
            }
            default: 
            {
                return eExecModeError;
            }
        }

        // Protect accessing manager<->state machine association
        {
            OsMutex::Scoped safeE( the_engine->lock( ) );
            OsMutex::Scoped scoped( manager->lock( ) );

            if ( parent )
            {
                // Create parent <-> child relationship
                OsMutex::Scoped safe( parent->manager( )->lock( ) );
                the_sm->attach_right_of( parent, ParentChild );
                the_sm->m_parent = parent;
            }

            // Short cut accessing the manager
            the_sm->m_manager = manager;

            // Before triggering 1st event
            // make sure that this state machine is alive
            // When it is re-used the flag is off
            the_sm->set_alive( true );

            // Create state machine <-> manager relationship
            the_sm->attach_right_of( manager.get( ), ManagerStateMachine );
        }

        // Set initial state by sending system message...
        the_sm->push_event( system( )->Ev_Sys_Create( ) );

        if ( exec_mode == RunAsync || 
             exec_mode == RunAsyncBatch )
        {
            // Wait the signal that state machine is started
            // that is transitioned to the 1st exec state
            // SEA_TRACE( " Wait for manager to start thread id:%x", manager->id( ) );
            the_sm->wait( );
        }
        else
        if ( exec_mode == RunDispatched )
        {
            // Right after start
            // - process initial event - system( )->Ev_Sys_Create( )
            // - transition to the very 1st state
            // In theory it can finish by the time it exists dispatch( )
            the_sm->dispatch( );
        }
    }

    if ( exec_mode == RunSync || exec_mode == RunParser )
    {
        ASSERT_DEBUG( manager.get( ) );

        // Check if the number of already launched state machines is less
        // then in the batch. This is just API misuse protection.
        if ( already_launched < count )
        {
            // Stay here until manager finishes
            manager->exec( );
        }
        else
        {
            ASSERT_DEBUG( false );
            return eStateMachineIsAlreadyActive;
        }
    }

    // Check if state machine has been compiled 
    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Transtition to the next state.
// -------------------------------------------------------------------------------------------------
void StateMachine::goto_next_state( i::Offset state, Event* event )
{
    // In counting state just gerenrate HeartBeat event and stay in 
    // the current state
    if( state == NEXT_STATE_COUNT ) 
    {
        int limit = get_body_field( m_current_state + Parser::eHeartBeatsNumber );
        if ( limit <= 0 )
        {
            return;
        }
        limit--;

        if ( m_counter < limit )
        {
            push_event( system( )->Ev_Sys_Heartbeat( ), event );
            m_counter++;
        }
        else
        if( m_counter == limit )
        {
            push_event( system( )->Ev_Sys_LastHeartbeat( ), event );
        }

        return;
    }

    // Reset counter for couting states
    m_counter = 0;

#if defined( SEA_LOG )
    Manager* manager = m_manager.get( );
    manager->log_state_transition( this, state );
#endif

    m_current_state = state;

    // Update the state stamp to indentify system events generated in this state
    m_stateStamp++;

    // Inherit event data from event originator
    push_event( system( )->Ev_Sys_OnEntry( ), event );

    start_state_timer( );
}

// -------------------------------------------------------------------------------------------------
// Start state timer
// -------------------------------------------------------------------------------------------------
SEA_INLINE void StateMachine::start_state_timer( )
{
    int rate = get_body_field( m_current_state + Parser::eHeartBeatInterval );
    if ( !rate )
    {
        return;
    }
    int count = get_body_field( m_current_state + Parser::eHeartBeatsNumber );

    if ( m_timer == NULL )
    {
        // Lazy instantiation
        m_timer << NEW Timer( 
            this,
            rate,
            count, 
            NULL,
            system( )->Ev_Sys_Heartbeat( ),
            system( )->Ev_Sys_LastHeartbeat( ) );
    }
    else
    {
        m_timer->reset( );
    }
    ASSERT_DEBUG( m_timer != NULL );

    m_timer->set_heart_beat_rate( rate );
    m_timer->set_heart_beats( count );
    m_timer->start( );
}

// -------------------------------------------------------------------------------------------------
// Print state machine body
// -------------------------------------------------------------------------------------------------
void StateMachine::print( )
{
#if defined( SEA_LOG )
    if ( !m_logging )
    {
        return;
    }
    SEA_TRACE( "-----------------------------------" );

    MetaSystemRegistryEntry* the_registry = registry( );

    SEA_TRACE( "%s", the_registry->m_name.c_str( ) );
    SEA_TRACE( "Exec Mode: %d", get_body_field( Parser::eExecMode ) );

    SEA_TRACE( "Initial State: %s( %d )",
        the_registry->m_offset2name[ get_body_field( Parser::eInitialStateIndex ) ].c_str( ),
        the_registry->m_offset2index[ get_body_field( Parser::eInitialStateIndex ) ] );

    int nos = get_body_field( Parser::eNumberOfStates );
    SEA_TRACE( "# of states: %d", nos );

    int offset = Parser::eExceptionStateIndex;

    for ( int i = 0; i < nos; i++ ) 
    {
        SEA_TRACE( "\nStateName: %s", the_registry->m_offset2name[ offset ].c_str( ) );
        SEA_TRACE( "HBI: %d", get_body_field( offset + Parser::eHeartBeatInterval ) );
        SEA_TRACE( "HBN: %d", get_body_field( offset + Parser::eHeartBeatsNumber ) );

        int nor = get_body_field( offset + Parser::eNumberOfRecords );
        offset += Parser::eEventRecords;
        
        for( int j = 0; j < nor; j++ )
        {
            m_manager->log_event( this, offset );
            offset += 3;
        }

    }

    SEA_TRACE( "-----------------------------------" );
#endif
}

// -------------------------------------------------------------------------------------------------
// Queue event
// -------------------------------------------------------------------------------------------------
Error StateMachine::push_event( EventId event_id, void* data /*=NULL */, bool sync_op /*= false */ )
{
    SEA_UNUSED( sync_op );

    if( event_id == system( )->Ev_Sys_NoEvent( ) )
    {
        return eOk;
    }

    Manager* manager = m_manager.get( );
    if ( !manager )
    {
        return eManagerNotPresent;
    }

    Event* the_event;
    {
        OsMutex::Scoped scoped( manager->lock( ) );

        // Check if state machine is alive
        if( !is_alive( ) ) 
        {
            return eMachineIsDead;
        }

        // Create the event
        the_event = NEW Event( this, event_id, data, sync_op );
        if ( !the_event )
        {
            return eOutOfMemory;
        }

        // Queue it
        get_event_queue( event_id, manager )->push( the_event );

        // Poke manager to process the event
        manager->add_event( );
    }

    // Wait for Sync event
    if ( sync_op )
    {
        ASSERT_DEBUG( manager->get_thread_handle( ) != OsThread::currentThreadId( ) );
        the_event->wait( );
    }

    // Release local reference
    the_event->release_ref( );

    // Notify hosting system about the event
    manager->dispatch_host( );
    
    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Queue event
// -------------------------------------------------------------------------------------------------
Error StateMachine::push_event( EventId event_id, Object* data, bool sync_op /*= false */ )
{
    SEA_UNUSED( sync_op );

    if( event_id == system( )->Ev_Sys_NoEvent( ) )
    {
        return eOk;
    }

    Manager* manager = m_manager.get( );
    if ( !manager )
    {
        return eManagerNotPresent;
    }

    Event* the_event;
    {
        OsMutex::Scoped scoped( manager->lock( ) );

        // Check if state machine is alive
        if( !is_alive( ) ) 
        {
            return eMachineIsDead;
        }

        // Create the event
        the_event = NEW Event( this, event_id, data, sync_op );
        if ( !the_event )
        {
            return eOutOfMemory;
        }

        // Queue it
        get_event_queue( event_id, manager )->push( the_event );

        // Poke manager to process the event
        manager->add_event( );
    }

    // Wait for Sync event
    if ( sync_op )
    {
        ASSERT_DEBUG( manager->get_thread_handle( ) != OsThread::currentThreadId( ) );
        the_event->wait( );
    }

    // Release local reference
    the_event->release_ref( );

    // Notify hosting system about the event
    manager->dispatch_host( );
    
    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Queue copy event
// -------------------------------------------------------------------------------------------------
Error StateMachine::push_event( Event* event )
{
    if( !event )
    {
        return eFailed;
    }

    if( event->get_event_id( ) == system( )->Ev_Sys_NoEvent( ) )
    {
        return eOk;
    }

    Manager* manager = m_manager.get( );
    if ( !manager )
    {
        return eManagerNotPresent;
    }

    Event* the_event;
    {
        OsMutex::Scoped scoped( manager->lock( ) );

        // Check if state machine is alive
        if( !is_alive( ) ) 
        {
            return eMachineIsDead;
        }

        // Create the event
        the_event = NEW Event( event );
        if ( !the_event )
        {
            return eOutOfMemory;
        }

        // Queue it
        get_event_queue( the_event->get_event_id( ), manager )->push( the_event );

        // Poke manager to process the event
        manager->add_event( );
    }

    // Wait for Sync event
    if ( the_event->is_sync( ) )
    {
        ASSERT_DEBUG( manager->get_thread_handle( ) != OsThread::currentThreadId( ) );

        the_event->wait( );

        // Copy result back, if provided by the action function
        // handling this event
        event->set_result< void* >( the_event->result< void * >( ) );
    }

    // Release local reference
    the_event->release_ref( );

    // Notify hosting system about the event
    manager->dispatch_host( );

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Get event queue for the provided event id
// -------------------------------------------------------------------------------------------------
SEA_INLINE Queue* StateMachine::get_event_queue( EventId event_id, Manager* manager )
{
    if( event_id & sysEventMask )
    {
        return manager->system_event_queue( );
    }

    if( event_id & intEventMask )
    {
        return manager->internal_event_queue( );
    }

    return manager->external_event_queue( );
}

// -------------------------------------------------------------------------------------------------
// Wait for state machine to finish. This call shoule be made from the thread
// other than the state machine
// -------------------------------------------------------------------------------------------------
Error StateMachine::wait( )
{
    Manager* manager = m_manager.get( );

    // Cannot wait on the signal if the call is made from the context of the same thread
    if( manager == NULL || manager->get_thread_handle( ) == OsThread::currentThreadId( ) )
    {
        return eWrongThreadContext;
    }

    // Thread should exist
    ASSERT_DEBUG( manager->isRunning( ) );

    // Wait for the exit lock to be released by destroy 
    // function of the state machine
    OsSemaphore::wait( );

    return eOk;
}

// -------------------------------------------------------------------------------------------------
// Wait for all state machine under the manager to finish
// -------------------------------------------------------------------------------------------------
Error StateMachine::wait_manager_to_finish( )
{
    Manager* manager = m_manager.get( );
    if ( !manager )
    {
        return eWaitFailed;
    }
    return manager->wait( );
}

// -------------------------------------------------------------------------------------------------
// Action function to stop state machine execution
// Cheating is going on here
// -------------------------------------------------------------------------------------------------
ACT_IMPL( SystemStateMachine, exit )
{
    StateMachine* sm = reinterpret_cast< StateMachine* >( ACT_EVENT_PARAM( ) );
    Manager* manager = sm->manager( ).get( );

    OsMutex::Scoped scoped( manager->lock( ) );

    // Timer's event has a reference to the state machine
    // Timer itself has a reference to the state machine
    // Stop and destroy timer now to break X-references
    sm->stop_state_timer( true );

    // Notify all children: parent is about to die
    for ( VIterator< StateMachine > it( sm, ParentChild ); *it; it++ )
    {
        // Pass parents handle to all the children
        it->push_event( Ev_Sys_ParentTerminated( ), sm );
    }

    // Tell my parent
    if( sm->parent( ).get( ) )
    {
        // Pass child's handle to the parent
        // This is redundant, but does not hurt
        sm->parent( )->push_event( StateMachine::system( )->Ev_Sys_ChildTerminated( ), sm );
    }

    // Tell manager to delete state machine 
    sm->push_event( StateMachine::system( )->Ev_Sys_Destroy( ) );
    
    // State machine can have a number of events sitting in the event queue
    // Setting alive to false would void them all but Ev_Sys_Destroy
    sm->set_alive( false );

    // Prepare for possible restart
    m_current_state = 0;
}

// -------------------------------------------------------------------------------------------------
// Manager calls this function on Ev_Sys_Destroy to remove the state machine from
// the list of active state machines
// -------------------------------------------------------------------------------------------------
void StateMachine::destroy( )
{
    Manager* the_manager = m_manager.get( );
    {
        ASSERT_DEBUG( the_manager );
        OsMutex::Scoped safeE( Engine::engine( )->lock( ) );
        OsMutex::Scoped safeM( the_manager->lock( ) );

        // Break relationship with manager
        detach( ManagerStateMachine );

        // Stop processing any events. This flag is 
        // true if user triggered Ev_Sys_Destroy
        m_alive = false;
    }

    if ( m_parent.get( ) )
    {
        the_manager = m_parent->manager( ).get( );
        ASSERT_DEBUG( the_manager );
        OsMutex::Scoped safeE( Engine::engine( )->lock( ) );
        OsMutex::Scoped safe( the_manager->lock( ) );

        // Detach myself from all children
        detach( ParentChild );

        // No need for parent reference
        // Pontentialy - breaking X-references
        m_parent = NULL;
    }

    // Now let whoever's waiting know that I'm done...
    OsSemaphore::signal( );

    // Did creator of the state machine wanted me 
    // to let someone know about the exist?
    if ( m_exit_notifier )
    {
        // Signal external object
        m_exit_notifier->release( );
        m_exit_notifier = NULL;
    }
}

// -------------------------------------------------------------------------------------------------
// Get system state machine to access system events and action functions
// -------------------------------------------------------------------------------------------------
SystemStateMachine* StateMachine::system( )
{
    if ( m_system == NULL )
    {
        m_system = NEW SystemStateMachine;
    }
    return m_system;
}


// -------------------------------------------------------------------------------------------------
// Set external OsNotifier to be triggered on state machine completion
// -------------------------------------------------------------------------------------------------
void StateMachine::set_waiter( OsNotifier* aNotifier )
{
    m_exit_notifier = aNotifier;
}

// -------------------------------------------------------------------------------------------------
// Switch state machine variant on the fly. Coolest API.
// -------------------------------------------------------------------------------------------------
bool StateMachine::switch_variant( i::Index variant )
{
    MetaSystemRegistryEntry* the_registry = registry( );

    sea::aref reg_variant = the_registry->m_variant_map[ variant ];
    if ( reg_variant == NULL )
    {
        // In order to add new variant just call create( ) with new one
        return false;
    }
    
    if ( m_manager != NULL )
    {
        OsMutex::Scoped scoped( m_manager->lock( ) );
        m_body = reg_variant;
    }
    else
    {
        m_body = reg_variant;
    }

    return true;
}

// -------------------------------------------------------------------------------------------------
// User API to force run a cycle of the state machine
// -------------------------------------------------------------------------------------------------
Error StateMachine::dispatch( )
{
    /*if ( exec_mode( ) != RunDispatched )
    {
        return eExecModeError;
    }*/

    if ( m_in_dispatcher )
    {
        return eDispatcherReEntry;
    }

    Manager* the_manager = manager( ).get( );
    if ( !the_manager )
    {
        return eManagerNotPresent;
    }
    m_in_dispatcher = true;

    Error code = the_manager->dispatch( );

    m_in_dispatcher = false;

    return code;
}

// -------------------------------------------------------------------------------------------------
// Set dispather priority. Set thread priority for the hosting dispather
// -------------------------------------------------------------------------------------------------
Error StateMachine::set_dispatcher_priority( OsThreadPriority priority )
{
    ASSERT_DEBUG( m_manager.get( ) );
    if ( m_manager != NULL )
    {
        m_manager->setPriority( priority );
        return eOk;
    }
    return eStartStateMachineFirst;
}

// -------------------------------------------------------------------------------------------------
// Change current state heart beat interval and heat beats number. 
// Not encouraged to use it, but if you really need it. 
// Lets say for calculated kinetic intervals and # of repetitions.
// -------------------------------------------------------------------------------------------------
void StateMachine::reset_state_properties( int time /* =-1 */, int counter /* = -1 */ )
{
    if ( time != -1 )
    {
        set_body_field( m_current_state + Parser::eHeartBeatInterval, time );
    }

    if( counter != -1 )
    {
        set_body_field( m_current_state + Parser::eHeartBeatsNumber, counter );
    }
    start_state_timer( );
}

// -------------------------------------------------------------------------------------------------
// Get terminate event. User state machine ca reuse system terminate event index 
// to instruct other state machine to terminate.
// -------------------------------------------------------------------------------------------------
int StateMachine::terminate_event_id( )
{ 
    return system( )->Ev_Sys_Terminate( ); 
}
